﻿using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Tomahawk.Serialization;
using Tomahawk.Serialization.SerialBoost;

namespace Tomahawk.Resources.Physics
{
    #region Materials
    public enum MaterialID
    {
        Unset,
        UserDefined,
        NotBouncySmooth,
        NotBouncyNormal,
        NotBouncyRough,
        NormalSmooth,
        NormalNormal,
        NormalRough,
        BouncySmooth,
        BouncyNormal,
        BouncyRough,
        NumMaterialTypes
    }
    #endregion

    #region Shape Descriptors
    [XmlInclude(typeof(PhysicsSphereShapeDesc))]
    [XmlInclude(typeof(PhysicsBoxShapeDesc))]
    [XmlInclude(typeof(PhysicsCapsuleShapeDesc))]
    [XmlInclude(typeof(PhysicsPlaneShapeDesc))]
    [XmlInclude(typeof(PhysicsMeshShapeDesc))]
    public abstract class PhysicsShapeDesc
    {
        // Transform
        public Vector3 localTransform = Vector3.Zero;
        public Quaternion localRotation = Quaternion.Identity;

        // Material
        public MaterialID materialID = MaterialID.UserDefined;
        public float elasticity = 0.5f;
        public float staticRoughness = 0.7f;
        public float dynamicRoughness = 0.6f;
    }

    // Sphere
    public sealed class PhysicsSphereShapeDesc : PhysicsShapeDesc
    {
        public float radius = 1.0f;
    }

    // Box
    public sealed class PhysicsBoxShapeDesc : PhysicsShapeDesc
    {
        public Vector3 Extents = new Vector3(1.0f, 1.0f, 1.0f);
    }

    // Capsule
    public sealed class PhysicsCapsuleShapeDesc : PhysicsShapeDesc
    {
        public float radius = 1.0f;
        public float height = 1.0f;
    }

    // Plane
    public sealed class PhysicsPlaneShapeDesc : PhysicsShapeDesc
    {
        public Vector3 up = Vector3.Up;
        public float d = 0.0f;
    }

    // Mesh
    public sealed class PhysicsMeshShapeDesc : PhysicsShapeDesc
    {
        public List<Vector3> vertexList = new List<Vector3>();
        public List<Vector3> indexList = new List<Vector3>();

        private static void ExtractData(List<Vector3> vertices, List<Vector3> indices, Model model)
        {
            Matrix[] bones_ = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(bones_);
            foreach (ModelMesh mm in model.Meshes)
            {
                Matrix xform = bones_[mm.ParentBone.Index];
                foreach (ModelMeshPart mmp in mm.MeshParts)
                {
                    int offset = vertices.Count;
                    Vector3[] a = new Vector3[mmp.NumVertices];
                    mm.VertexBuffer.GetData<Vector3>(mmp.StreamOffset + mmp.BaseVertex * mmp.VertexStride,
                        a, 0, mmp.NumVertices, mmp.VertexStride);
                    for (int i = 0; i != a.Length; ++i)
                        Vector3.Transform(ref a[i], ref xform, out a[i]);
                    vertices.AddRange(a);

                    if (mm.IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
                        throw new Exception(
                            String.Format("Model uses 32-bit indices, which are not supported."));
                    short[] s = new short[mmp.PrimitiveCount * 3];
                    mm.IndexBuffer.GetData<short>(mmp.StartIndex * 2, s, 0, mmp.PrimitiveCount * 3);

                    Vector3[] tvi = new Vector3[mmp.PrimitiveCount];

                    for (int i = 0; i != mmp.PrimitiveCount; ++i)
                    {
                        tvi[i].X = s[i * 3 + 2] + offset;
                        tvi[i].Y = s[i * 3 + 1] + offset;
                        tvi[i].Z = s[i * 3 + 0] + offset;
                    }
                    indices.AddRange(tvi);
                }
            }
        }
    
        public void LoadFromModel(Model model)
        {
            vertexList = new List<Vector3>();
            indexList = new List<Vector3>();
            ExtractData(vertexList, indexList, model);
        }
    }

    #endregion

    #region Body Descriptors
    public sealed class PhysicsBodyDesc
    {
        [XmlAttribute("Name")]
        public string name;

        [XmlElement("Mass")]
        public float mass = 1.0f;

        [XmlElement("Shapes")]
        public SerializableList<PhysicsShapeDesc> shapes = new SerializableList<PhysicsShapeDesc>();
    }
    #endregion

    #region Constraint Descriptors
    public class PhysicsConstraintDesc
    {
        public string body1;
        public string body2;
    }
    #endregion

    #region Actor Descriptors

    public enum PhysicsActorType
    {
        StaticActor = 0,     // The actor is considered static, so no update is needed
        InterpActor = 1,     // The actor movement is controlled by the game logic. The physics actor position is updated from the logic actor position
        DynamicActor = 2,     // The actor movement is controlled by the game physics. The logic actor position is updated from the physics actor position
        TriggerActor = 3,     // The actor can't be collided. It will generate events to notify other actors when they touch or untouch the trigger
    }

    public sealed class PhysicsActorDesc : IDisposable
    {
        [XmlAttribute("Name")]
        public string name;

        [XmlElement("Type")]
        public PhysicsActorType type;

        [XmlElement("Bodies")]
        public SerializableList<PhysicsBodyDesc> bodies = new SerializableList<PhysicsBodyDesc>();

        [XmlElement("Constraints")]
        public SerializableList<PhysicsConstraintDesc> constraints = new SerializableList<PhysicsConstraintDesc>();

        #region IDisposable Members

        public void Dispose()
        {
        }

        #endregion
    }
    #endregion
}
